package com.swak.registry;

import java.io.Serializable;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;

import com.swak.Constants;
import com.swak.codec.Encodes;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;

import lombok.Getter;

/**
 * URL
 * 
 * @author lifeng
 * @date 2021年2月26日 下午1:48:59
 */
public class URL implements Serializable {

	private static final long serialVersionUID = 1L;

	static Pattern COMMA_SPLIT_PATTERN = Pattern.compile("\\s*[,]+\\s*");
	@Getter
	private final String protocol;
	@Getter
	private final String username;
	@Getter
	private final String password;
	@Getter
	private final String host;
	@Getter
	private final int port;
	@Getter
	private final String path;
	private final Map<String, String> parameters;
	private String full;

	public URL(String protocol, String host, int port, Map<String, String> parameters) {
		this(protocol, null, null, host, port, null, parameters);
	}

	public URL(String protocol, String host, int port, String path, Map<String, String> parameters) {
		this(protocol, null, null, host, port, path, parameters);
	}

	public URL(String protocol, String username, String password, String host, int port, String path,
			Map<String, String> parameters) {
		if (StringUtils.isEmpty(username) && StringUtils.isNotEmpty(password)) {
			throw new IllegalArgumentException("Invalid url, password without username!");
		}
		this.protocol = protocol;
		this.username = username;
		this.password = password;
		this.host = host;
		this.port = Math.max(port, 0);
		while (path != null && path.startsWith("/")) {
			path = path.substring(1);
		}
		this.path = path;
		if (parameters == null) {
			parameters = Maps.newHashMap();
		}
		this.parameters = Maps.newHashMap(parameters);
	}

	public String getApplication() {
		return this.getParameter(Constants.Application_KEY);
	}

	public String getModule() {
		return this.getParameter(Constants.Module_Key);
	}

	public String getService() {
		return this.getParameter(Constants.Service_Key);
	}

	public String getCategory() {
		return this.getParameter(Constants.Category_Key);
	}

	public URL addParameter(String key, String value) {
		parameters.put(key, value);
		return this;
	}

	public String getParameter(String key) {
		return parameters.get(key);
	}

	public String getParameter(String key, String defaultValue) {
		String value = getParameter(key);
		return StringUtils.isEmpty(value) ? defaultValue : value;
	}

	public Integer getIntParameter(String key, Integer defaultValue) {
		String value = getParameter(key);
		return StringUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value);
	}

	public String[] getParameter(String key, String[] defaultValue) {
		String value = getParameter(key);
		return StringUtils.isEmpty(value) ? defaultValue : COMMA_SPLIT_PATTERN.split(value);
	}

	public List<String> getParameter(String key, List<String> defaultValue) {
		String value = getParameter(key);
		if (StringUtils.isEmpty(value)) {
			return defaultValue;
		}
		String[] strArray = COMMA_SPLIT_PATTERN.split(value);
		return Arrays.asList(strArray);
	}

	public boolean getParameter(String key, boolean defaultValue) {
		String value = getParameter(key);
		return StringUtils.isEmpty(value) ? defaultValue : Boolean.parseBoolean(value);
	}

	public int getParameter(String key, int defaultValue) {
		String value = getParameter(key);
		if (StringUtils.isEmpty(value)) {
			return defaultValue;
		}
		return Integer.parseInt(value);
	}

	public String getAddress() {
		return port <= 0 ? host : host + ':' + port;
	}

	public String toFullString() {
		if (full != null) {
			return full;
		}
		return full = buildString();
	}

	private String buildString() {
		StringBuilder buf = new StringBuilder();
		if (StringUtils.isNotEmpty(protocol)) {
			buf.append(protocol);
			buf.append("://");
		}
		if (StringUtils.isNotEmpty(username)) {
			buf.append(username);
			if (StringUtils.isNotEmpty(password)) {
				buf.append(":");
				buf.append(password);
			}
			buf.append("@");
		}
		String host = this.host;
		if (StringUtils.isNotEmpty(host)) {
			buf.append(host);
			if (port > 0) {
				buf.append(":");
				buf.append(port);
			}
		}
		String path = this.path;
		if (StringUtils.isNotEmpty(path)) {
			buf.append("/");
			buf.append(path);
		}
		boolean first = true;
		for (Map.Entry<String, String> entry : new TreeMap<>(this.parameters).entrySet()) {
			if (StringUtils.isNotEmpty(entry.getKey())) {
				if (first) {
					buf.append("?");
					first = false;
				} else {
					buf.append("&");
				}
				buf.append(entry.getKey());
				buf.append("=");
				buf.append(entry.getValue() == null ? "" : entry.getValue().trim());
			}
		}
		return buf.toString();
	}

	public String toRootPath() {
		return Constants.PATH_SEPARATE + this.protocol;
	}

	public String toModulePath() {
		if (StringUtils.isBlank(this.getModule())) {
			return this.toRootPath();
		}
		return this.toRootPath() + Constants.PATH_SEPARATE + this.getModule();
	}

	public String toServicePath() {
		if (StringUtils.isBlank(this.getService())) {
			return this.toModulePath();
		}
		return this.toModulePath() + Constants.PATH_SEPARATE + this.getService();
	}

	public String toCategoryPath() {
		if (StringUtils.isBlank(this.getCategory())) {
			return this.toServicePath();
		}
		return this.toServicePath() + Constants.PATH_SEPARATE + this.getCategory();
	}

	public String[] toCategoryPaths() {
		String[] categorys = null;
		if (!StringUtils.isBlank(this.getCategory())) {
			categorys = this.getCategory().split(Constants.IDS_SEPARATE);
		}
		String[] paths = categorys == null ? new String[] { this.toServicePath() } : new String[categorys.length];
		if (categorys != null) {
			for (int i = 0; i < categorys.length; i++) {
				paths[i] = this.toServicePath() + Constants.PATH_SEPARATE + categorys[i];
			}
		}
		return paths;
	}

	public String toUrlPath() {
		return this.toCategoryPath() + "/" + Encodes.urlEncode(this.toFullString());
	}

	/**
	 * protocol、serviceKey 这两个属性匹配或者serviceKey 都不存在
	 * 
	 * @param url
	 * @return
	 */
	public boolean match(URL url) {
		String module = this.getModule();
		String otherModule = url.getModule();
		String service = this.getService();
		String otherService = url.getService();
		if (this.protocol != null && url.protocol != null && this.protocol.equals(url.protocol)
				&& ((module != null && otherModule != null && module.equals(otherModule))
						|| module == null && otherModule == null)
				&& ((service != null && otherService != null && service.equals(otherService))
						|| service == null && otherService == null)) {
			return true;
		}
		return false;
	}

	public InetSocketAddress toInetSocketAddress() {
		return new InetSocketAddress(host, port);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((host == null) ? 0 : host.hashCode());
		result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
		result = prime * result + ((password == null) ? 0 : password.hashCode());
		result = prime * result + ((path == null) ? 0 : path.hashCode());
		result = prime * result + port;
		result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
		result = prime * result + ((username == null) ? 0 : username.hashCode());
		result = prime * result + ((this.getApplication() == null) ? 0 : this.getApplication().hashCode());
		result = prime * result + ((this.getModule() == null) ? 0 : this.getModule().hashCode());
		result = prime * result + ((this.getService() == null) ? 0 : this.getService().hashCode());
		result = prime * result + ((this.getCategory() == null) ? 0 : this.getCategory().hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		URL other = (URL) obj;
		if (!StringUtils.isEquals(host, other.host)) {
			return false;
		}
		if (parameters == null) {
			if (other.parameters != null) {
				return false;
			}
		} else if (!parameters.equals(other.parameters)) {
			return false;
		}
		if (!StringUtils.isEquals(password, other.password)) {
			return false;
		}
		if (!StringUtils.isEquals(path, other.path)) {
			return false;
		}
		if (port != other.port) {
			return false;
		}
		if (!StringUtils.isEquals(protocol, other.protocol)) {
			return false;
		}
		if (!StringUtils.isEquals(username, other.username)) {
			return false;
		}
		if (!StringUtils.isEquals(this.getApplication(), other.getApplication())) {
			return false;
		}
		if (!StringUtils.isEquals(this.getModule(), other.getModule())) {
			return false;
		}
		if (!StringUtils.isEquals(this.getService(), other.getService())) {
			return false;
		}
		if (!StringUtils.isEquals(this.getCategory(), other.getCategory())) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return this.toFullString();
	}

	public static URL valueOf(URL url) {
		return new URL(url.protocol, url.username, url.password, url.host, url.port, url.path, url.parameters);
	}

	public static URL valueOf(String url) {
		if (url == null || (url = url.trim()).length() == 0) {
			throw new IllegalArgumentException("url == null");
		}
		String protocol = null;
		String username = null;
		String password = null;
		String host = null;
		int port = 0;
		String path = null;
		Map<String, String> parameters = null;
		int i = url.indexOf('?'); // separator between body and parameters
		if (i >= 0) {
			String[] parts = url.substring(i + 1).split("&");
			parameters = new HashMap<>();
			for (String part : parts) {
				part = part.trim();
				if (part.length() > 0) {
					int j = part.indexOf('=');
					if (j >= 0) {
						String key = part.substring(0, j);
						String value = part.substring(j + 1);
						parameters.put(key, value);
					} else {
						parameters.put(part, part);
					}
				}
			}
			url = url.substring(0, i);
		}
		i = url.indexOf("://");
		if (i >= 0) {
			if (i == 0) {
				throw new IllegalStateException("url missing protocol: \"" + url + "\"");
			}
			protocol = url.substring(0, i);
			url = url.substring(i + 3);
		} else {
			// case: file:/path/to/file.txt
			i = url.indexOf(":/");
			if (i >= 0) {
				if (i == 0) {
					throw new IllegalStateException("url missing protocol: \"" + url + "\"");
				}
				protocol = url.substring(0, i);
				url = url.substring(i + 1);
			}
		}

		i = url.indexOf('/');
		if (i >= 0) {
			path = url.substring(i + 1);
			url = url.substring(0, i);
		}
		i = url.lastIndexOf('@');
		if (i >= 0) {
			username = url.substring(0, i);
			int j = username.indexOf(':');
			if (j >= 0) {
				password = username.substring(j + 1);
				username = username.substring(0, j);
			}
			url = url.substring(i + 1);
		}
		i = url.lastIndexOf(':');
		if (i >= 0 && i < url.length() - 1) {
			if (url.lastIndexOf('%') > i) {
				// ipv6 address with scope id
				// e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
				// see https://howdoesinternetwork.com/2013/ipv6-zone-id
				// ignore
			} else {
				port = Integer.parseInt(url.substring(i + 1));
				url = url.substring(0, i);
			}
		}
		if (url.length() > 0) {
			host = url;
		}

		return new URL(protocol, username, password, host, port, path, parameters);
	}
}
